package rtda

import (
	"encoding/hex"
	"strings"

	"github.com/ethereum/go-ethereum/rlp"
	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/common"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/rtda/heap"
	pbCommon "hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/execute"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/account"
	"hundsun.com/hsl/hschain/store/extend/contract"
)

func (thread *Thread) InvokeESDaoMethod(currentFrame *Frame, newFrame *Frame, method *heap.Method) {
	//st := time.Now().UnixNano()
	jlog := thread.Log
	if method.Name == common.ESDaoPutMethodName {
		var putKey string
		var putBytes []byte
		for i := uint(0); i < method.ParamSlotCount; i++ {
			args := newFrame.GetLocalVar(i)
			ref := args.Ref
			if i == 1 {
				putKey = ref.JSToGoStr()
				jlog.Debugf("ESDao put key %s", putKey)
			} else if i == 2 {
				putBytes, _ = thread.RlpEncode(&args, "")
				//putObj := ref
				jlog.Debugf("ESDao put bytes %v ", hex.EncodeToString(putBytes))
			}
		}
		//存储操作需要根据占用的空间扣除gas
		gasLeft := thread.GasLeft.Load() - int64(len(putBytes))
		thread.GasLeft.Store(gasLeft)
		if thread.GasLeft.Load() <= 0 {
			thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_OUT_OF_GAS
			thread.Log.Warnf("InvokeESDaoMethod execute out of gas, current gasleft: %d", thread.GasLeft.Load())
			panic("out of gas limit")
		}
		ctStat := &pbCommon.ContractStatus{
			Address:   thread.ContractName,
			Namespace: "ESDao",
			Key:       putKey,
			Value:     putBytes,
		}
		kv, _ := contract.StatusKV(ctStat, common.ContractStatusTypeEsdao)
		thread.ExecCtx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		thread.GroupedTx.TxExecRst.Kvs = append(thread.GroupedTx.TxExecRst.Kvs, kv)
	} else if method.Name == common.ESDaoGetMethodName {
		var getKey string
		var getObj heap.Slot
		for i := uint(0); i < method.ParamSlotCount; i++ {
			args := newFrame.GetLocalVar(i)
			ref := args.Ref
			if i == 1 {
				getKey = ref.JSToGoStr()
				jlog.Debugf("ESDao get key %s", getKey)
			}
			if i == 2 {
				getObj = args
			}
		}
		if getKey != "" {
			var getBytes []byte

			thread := currentFrame.Thread
			csStatusBytes, err := thread.ExecCtx.Get(contract.GenContractStatusKey(getKey, thread.ContractName, common.ContractStatusTypeEsdao))
			if err != nil {
				getBytes = nil
			}

			// 存储的是contract status，再做一次protobuf解码
			var cStatus = &pbCommon.ContractStatus{}
			err = proto.Unmarshal(csStatusBytes, cStatus)
			if err != nil {
				getBytes = nil
			} else {
				getBytes = cStatus.Value
			}

			thread.PopFrame()
			invokerFrame := thread.TopFrame()
			//如果没有get到数据直接返回空指针
			if getBytes == nil {
				invokerFrame.PushL(heap.EmptySlot, false)
			} else {
				jlog.Debugf("ES get bytes is %s", hex.EncodeToString(getBytes))
				var ref *heap.Slot
				ref, err = thread.DecodeReferenceObject(&getObj, getBytes)
				if err != nil {
					jlog.Errorf("ESDao get method decode reference type %s failed,error %s",
						getObj.Ref.Class.Name, err.Error())
					panic("ESDao get method decode reference type " + getObj.Ref.Class.Name + " failed")
				}
				invokerFrame.PushL(*ref, false)

			}

		}
	}
	//et := time.Now().UnixNano()
	//jlog.Infof("Invoke ESDao method last %d",(et-st)/1e6)
}

func (thread *Thread) InvokeEventsMethod(newFrame *Frame, method *heap.Method) {
	//st := time.Now().UnixNano()
	jlog := thread.Log
	event := pbCommon.ContractEvent{}
	event.Name = method.Name
	var eventDataBuf [][]byte
	for i := uint(1); i < method.ParamSlotCount; i++ {
		ref := newFrame.GetLocalVar(i).Ref
		if i > 0 && i < 4 {
			eventIndex := ref.JSToGoStr()
			if i == 1 {
				event.IndexedParamFirst = eventIndex
			} else if i == 2 {
				event.IndexedParamSecond = eventIndex
			} else if i == 3 {
				event.IndexedParamThird = eventIndex
			}
		} else {
			slot := newFrame.GetLocalVar(i)
			var className string
			var classNameBytes []byte
			var desc string
			if ref != nil {
				className = ref.Class.Name
				desc = ""
			} else {
				className = method.GetParameterTypes()[i-1].Name
				desc = className
			}
			classNameBytes = []byte(className)
			objBuff, err := thread.RlpEncode(&slot, desc)
			if err != nil {
				jlog.Errorf("EventDao endcode params %d error %s", i, err.Error())
			}

			var tmpBuf [2][]byte
			tmpBuf[0] = classNameBytes
			tmpBuf[1] = objBuff
			encBuf, err := rlp.EncodeToBytes(tmpBuf)
			if err != nil {
				jlog.Errorf("EventDao endcode params %d error %s", i, err.Error())
			}
			eventDataBuf = append(eventDataBuf, encBuf)
			if thread.IsTwoSlotsVar(className) {
				i++
			}
		}
	}
	buff, err := rlp.EncodeToBytes(eventDataBuf)
	if err != nil {
		jlog.Errorf("EventDao endcode all params  error %s", err.Error())
	}
	event.Data = buff
	jlog.Debugf("Emit event name is %s,first index is %s,second index is %s,third index is %s ", event.Name, event.IndexedParamFirst,
		event.IndexedParamSecond, event.IndexedParamThird)
	//jlog.Debugf("Emit event data is %v",event.Data)
	jlog.Debugf("Emit event data is %s", hex.EncodeToString(event.Data))
	event.ContractAddress = thread.ContractAddress
	event.BlockNumber = thread.GroupedTx.TxExecRst.Receipt.BlockNumber
	event.TransactionHash = thread.GroupedTx.TxExecRst.Receipt.TxHash
	thread.GroupedTx.TxExecRst.Receipt.Events = append(thread.GroupedTx.TxExecRst.Receipt.Events, &event)
	//et := time.Now().UnixNano()
	//jlog.Infof("Invoke Event method last %d",(et-st)/1e6)

}
func (thread *Thread) InvokeTransferMethod(newFrame *Frame, method *heap.Method) {
	jlog := thread.Log
	var contractAccount *pbCommon.Account
	var err error
	tx := thread.GroupedTx.Tx

	acc := account.NewAccountDB(thread.ExecCtx)
	contractAccount, err = acc.GetAccount(thread.ContractName)
	if err != nil {
		thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_INSUFFICIENT_FUNDS
		jlog.Error("Transfer contract account %s not found", thread.ContractName)
		panic("Transfer contract account not found" + err.Error())
	}
	if tx.Value > 0 {
		contractAccount.Balance += tx.Value
	}
	var toAddr string
	var value int64
	for i := uint(1); i < method.ParamSlotCount; i++ {
		ref := newFrame.GetLocalVar(i).Ref
		if i == 1 {
			toAddr = ref.JSToGoStr()

		} else if i == 2 {
			if ref == nil {
				value = newFrame.GetLongVar(i)
			} else {
				if ref.Class.Name == "java/lang/Long" {
					value = ref.Fields.([]heap.Slot)[0].Val
				} else {
					value = 0
				}
			}
		}
	}
	jlog.Infof("Contract transfer to address:%s, value: %d\n", toAddr, value)
	if value < 0 {
		thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_AMOUNT_NEGATIVE
		jlog.Errorf("Transfer value is negative")
		panic("Transfer value is negative")
	} else if int64(contractAccount.Balance-uint64(value)) < 0 {
		//合约账户余额不足
		thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_INSUFFICIENT_FUNDS
		jlog.Errorf("Contract account [%s] have no sufficient funds", thread.ContractAddress)
		panic("Contract account have no sufficient funds")
	}

	contractAccount.Balance -= uint64(value)
	var toAccount = &pbCommon.Account{}
	toAccount, err = acc.GetAccount(toAddr)
	if err != nil {
		toAccount = &pbCommon.Account{
			InternalAddress: toAddr,
			Balance:         0,
			Type:            pbCommon.Account_USER,
		}
	}

	//写入执行结果,返回boss给定时任务提交写入存储
	accountContract := &pbCommon.Account{InternalAddress: thread.ContractName, Balance: contractAccount.Balance, Type: pbCommon.Account_CONTRACT}
	toAccount.Balance += uint64(value)
	toAccountBytes, err := proto.Marshal(toAccount)
	if err != nil {
		thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_JAVA_OTHER_EXCEPTION
		panic("Transfer marshal to account failed ,error is  " + err.Error())
	}
	//判断transfer to是否是新的中间地址
	toIsNewMidAddr := true
	for _, kv := range thread.GroupedTx.TxExecRst.Kvs {
		if !strings.HasPrefix(string(kv.Key), db.NamespaceAccount) {
			continue
		}
		if string(account.GenAccountKey(toAddr)) == string(kv.Key) {
			toIsNewMidAddr = false
			//是from地址,更新余额
			kv.Value = toAccountBytes
			break
		}
	}
	//更改地址读写状态
	contractAccBytes, err := proto.Marshal(accountContract)
	if err != nil {
		thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_JAVA_OTHER_EXCEPTION
		panic("Transfer update contract address account balance failed ,error is  " + err.Error())
	}
	acKv := &pbCommon.KeyValue{Key: account.GenAccountKey(accountContract.InternalAddress), Value: contractAccBytes}
	toKv := &pbCommon.KeyValue{Key: account.GenAccountKey(toAccount.InternalAddress), Value: toAccountBytes}
	thread.GroupedTx.TxExecRst.Kvs = append(thread.GroupedTx.TxExecRst.Kvs, acKv)
	thread.ExecCtx.GetStateHandle().SetLocal(acKv.Key, acKv.Value)
	if toIsNewMidAddr {
		//新的中间地址状态需要添加到执行结果
		addressToRw := &execute.RWStatus{
			Key:      []byte(toAddr),
			RwStatus: execute.RwStatusType_WriteStatus,
		}
		thread.GroupedTx.TxExecRst.Kvs = append(thread.GroupedTx.TxExecRst.Kvs, toKv)
		thread.ExecCtx.GetStateHandle().SetLocal(toKv.Key, toKv.Value)
		thread.GroupedTx.VarsRWStatus = append(thread.GroupedTx.VarsRWStatus, addressToRw)
	}
}

func (thread *Thread) InvokeTxDaoMethod(method *heap.Method) {
	tx := thread.GroupedTx.Tx
	var ref heap.Slot
	isLongOrDouble := true
	if method.Name == common.TxDaoGetBlockNumberMethodName {
		ref = heap.NewLongSlot(int64(thread.ExecCtx.GetHeight()))
	} else if method.Name == common.TxDaoGetBlockTimeMethodName {
		ref = heap.NewLongSlot(int64(thread.ExecCtx.GetBlockTime()))
	} else if method.Name == common.TxDaoGetFromMethodName {
		ref = heap.NewRefSlot(thread.Runtime.JSFromGoStrNoIntern(tx.From))
		isLongOrDouble = false
	} else if method.Name == common.TxDaoGetValueMethodName {
		ref = heap.NewLongSlot(int64(tx.Value))
	} else {
		thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_JAVA_ILLEGAL_ARGUMENT_EXCEPTION
		panic("TxDao not support method " + method.Name)
	}

	thread.PopFrame()
	invokerFrame := thread.TopFrame()
	invokerFrame.PushL(ref, isLongOrDouble)
}

func (thread *Thread) InvokeMainMethod(newFrame *Frame, method *heap.Method) {
	jlog := thread.Log
	var obj *heap.Object
	var args []byte
	for i := uint(0); i < method.ParamSlotCount; i++ {
		ref := newFrame.GetLocalVar(i).Ref
		if i == 0 {
			obj = ref
		} else if i == 1 {
			args = ref.GetGoBytes()
		}
	}
	jlog.Infof("InvokeMainMethod class is %s\n", obj.Class.Name)
	if thread.InitedFlag {
		//deploy初始化变量
		var deployPayload DeployPayLoad
		err := rlp.DecodeBytes(args, &deployPayload)
		if err != nil {
			jlog.Errorf("Main method decode javatype failed " + err.Error())
			panic("Main method decode javatype failed " + err.Error())
		}

		for _, param := range deployPayload.JavaParams {
			fieldName := param.FieldName
			fieldObj, err := thread.DecodeDepolyInitParam(param.FieldObj)
			if err != nil {
				jlog.Errorf("Decode deplpy init field " + fieldName + " failed " + err.Error())
				panic("Decode deplpy init field " + fieldName + " failed " + err.Error())
			}
			//不需要初始化
			if fieldObj == heap.EmptySlot {
				continue
			}
			//将初始化变量写入交易回执的nskvs中
			contractClassName := thread.MainClass[:len(thread.MainClass)-4]
			class := thread.Runtime.BootLoader().LoadClass(contractClassName)
			for _, f := range class.Fields {
				if f.Name == fieldName {
					nsKvs := thread.StoreDataToStorage(f, fieldObj)
					for _, nsKv := range nsKvs {
						ctStat := &pbCommon.ContractStatus{
							Address:   thread.ContractName,
							Namespace: "ClassField",
							Key:       nsKv.Key,
							Value:     nsKv.Value,
						}
						kv, _ := contract.StatusKV(ctStat, common.ContractStatusTypeField)
						thread.ExecCtx.GetStateHandle().SetLocal(kv.Key, kv.Value)
						thread.GroupedTx.TxExecRst.Kvs = append(thread.GroupedTx.TxExecRst.Kvs, kv)
					}
					break
				}
			}
		}
	} else {
		var className string
		var methodName string
		var commonParam *CommonParam
		var methodArgs []heap.Slot
		var paramArgs []heap.Slot
		var err error
		methodArgs = append(methodArgs, heap.NewRefSlot(obj))
		commonParam, paramArgs, err = thread.DecodeCommonParam(args)
		if err != nil {
			jlog.Errorf("Decode common parameters failed,err:", err.Error())
			panic("Decode common parameters failed")
		}
		methodArgs = append(methodArgs, paramArgs...)
		//className
		className = commonParam.ClassName
		//methodName
		methodName = commonParam.MethodName
		//methodDesc
		methodDesc := commonParam.MethodDesc
		//var param ParamJavaType
		jlog.Debugf("Decode result className = %s,methodName = %s,methodDesc = %s\n", className, methodName, methodDesc)
		//parameter list

		contractClassName := thread.MainClass[:len(thread.MainClass)-4]
		class := thread.Runtime.BootLoader().LoadClass(contractClassName)
		method = class.GetContractMethod(methodName, methodDesc)
		if method == nil {
			jlog.Errorf("Can't find the contract method name : %s,desc:%s", methodName, methodDesc)
			panic("Can't find the contract method name : " + methodName + ",desc:" + methodDesc)
		}
		newFrame = thread.NewFrame(method)
		thread.PushFrame(newFrame)
		var i, j uint
		newFrame.SetLocalVar(0, methodArgs[0])
		j = 1
		for i = 0; i < method.ParamCount; i++ {
			//从数组恢复HashMap类型
			if commonParam.Params[i].TypeName == "java.util.HashMap" {
				//hashMapClass := thread.Runtime.BootLoader().LoadClass("java/util/HashMap")
				//hashMapObj := hashMapClass.NewObj()
				if paramArgs[i].Ref != nil {
					hashMapObj, err := thread.GenerateMapList(paramArgs[i].Ref)
					if err == nil && hashMapObj != nil {
						newFrame.SetLocalVar(j, heap.NewRefSlot(hashMapObj))
						hashMapClass := thread.Runtime.BootLoader().LoadClass("java/util/HashMap")
						constructMethod := hashMapClass.GetDefaultConstructor()
						newInitFrame := thread.NewFrame(constructMethod)
						newInitFrame.SetLocalVar(0, heap.NewRefSlot(hashMapObj))
						thread.PushFrame(newInitFrame)
					}
				} else {
					newFrame.SetLocalVar(j, heap.EmptySlot)
				}
			} else if commonParam.Params[i].TypeName == "java.util.ArrayList" {
				if paramArgs[i].Ref != nil {
					listObj, err := thread.GenerateMapList(paramArgs[i].Ref)
					if err == nil && obj != nil {
						newFrame.SetLocalVar(j, heap.NewRefSlot(listObj))
						arrayListClass := thread.Runtime.BootLoader().LoadClass("java/util/ArrayList")
						constructMethod := arrayListClass.GetDefaultConstructor()
						newInitFrame := thread.NewFrame(constructMethod)
						newInitFrame.SetLocalVar(0, heap.NewRefSlot(listObj))
						thread.PushFrame(newInitFrame)
					}
				} else {
					newFrame.SetLocalVar(j, heap.EmptySlot)
				}
			} else {
				//long or double 类型占用两个slot
				newFrame.SetLocalVar(j, paramArgs[i])
				if thread.IsTwoSlotsVar(commonParam.Params[i].TypeName) {
					j++
				}
			}
			j++
		}
	}
}
